home *** CD-ROM | disk | FTP | other *** search
/ Mac Mania 5 / MacMania 5.toast / / Internet software / NewsWatcher / NW Source / Source / header.c < prev    next >
Text File  |  1997-01-09  |  30KB  |  1,077 lines

  1. /*----------------------------------------------------------------------------
  2.  
  3.     header.c
  4.     
  5.     This module exports several utility functions for working with news
  6.     and mail headers.
  7.     
  8.     Copyright © 1994-1997, Northwestern University.
  9.  
  10. ----------------------------------------------------------------------------*/
  11.  
  12. #include <string.h>
  13. #include <stdio.h>
  14. #include <ctype.h>
  15.  
  16. #include "glob.h"
  17. #include "header.h"
  18. #include "menus.h"
  19. #include "newswatcher.h"
  20. #include "strutil.h"
  21. #include "net.h"
  22. #include "memutil.h"
  23. #include "ic.h"
  24.  
  25.  
  26.  
  27. typedef enum ELineBreakMapping {
  28.     kMapToSpace,                    /* map line breaks to space */
  29.     kMapToComma,                    /* map line breaks to comma */
  30.     kMapToCR,                        /* map line breaks to CR */
  31.     kNoMapCR                        /* no line break mapping */
  32. } ELineBreakMapping;
  33.  
  34.  
  35.  
  36. /*----------------------------------------------------------------------------
  37.     LocateHeaderLine
  38.     
  39.     Locate a header line.
  40.     
  41.     Entry:    text = handle to header text.
  42.             hdrEnd = length of header text.
  43.             key = C-format header to locate, not including 
  44.                 the terminating ":".
  45.             
  46.     Exit:    function result = true if header found, else false.
  47.             *start = offset in text of start of header contents.
  48.             *len = length of header contents.
  49.             
  50.     This function only returns the first part of "folded" header lines.
  51. ----------------------------------------------------------------------------*/
  52.  
  53. static Boolean LocateHeaderLine (Handle text, long hdrEnd, char *key, long *start, long *len)
  54. {
  55.     long keyLen;
  56.     char *p, *pEnd, *q;
  57.  
  58.     keyLen = strlen(key);
  59.     p = *text;
  60.     pEnd = *text + hdrEnd;
  61.     while (p < pEnd) {
  62.         if (MyStrNEqual(p, key, keyLen)) {
  63.             p += keyLen;
  64.             while (p < pEnd && isLWSP(*p)) p++;
  65.             if (*p == ':') {
  66.                 p++;
  67.                 while (p < pEnd && isLWSP(*p)) p++;
  68.                 q = p;
  69.                 while (q < pEnd && *q != CR) q++;
  70.                 q--;
  71.                 while (q >= p && isLWSP(*q)) q--;
  72.                 q++;
  73.                 *start = p - *text;
  74.                 *len = q - p;
  75.                 return true;
  76.             }
  77.         }
  78.         while (p < pEnd && *p != CR) p++;
  79.         p++;
  80.     }
  81.     return false;
  82. }
  83.  
  84.  
  85.  
  86. /*----------------------------------------------------------------------------
  87.     LocateArticleHeaderLine
  88.     
  89.     Locate a message header line in an article.
  90.     
  91.     Entry:    text = handle to article text.
  92.             key = C-format header to locate, not including 
  93.                 the terminating ":".
  94.             
  95.     Exit:    function result = true if header found, else false.
  96.             *start = offset in text of start of header contents.
  97.             *len = length of header contents.
  98.             
  99.     This function only returns the first part of "folded" header lines.
  100.     The caller should call LocateContinuationHeaderLine below to locate 
  101.     the continuation lines.
  102. ----------------------------------------------------------------------------*/
  103.  
  104. static Boolean LocateArticleHeaderLine (Handle text, char *key, long *start, long *len)
  105. {
  106.     long hdrEnd;
  107.  
  108.     hdrEnd = Munger(text, 0, CRCR, 2, nil, 0);
  109.     if (hdrEnd < 0) hdrEnd = MyGetHandleSize(text);
  110.     return LocateHeaderLine(text, hdrEnd, key, start, len);
  111. }
  112.  
  113.  
  114.  
  115. /*----------------------------------------------------------------------------
  116.     LocateContinuationHeaderLine
  117.     
  118.     Locate a continuation message header line in an article.
  119.     
  120.     Entry:    text = handle to article text.
  121.             *start = offset in text of first character following end of
  122.                 previous line in "folded" header line.
  123.             
  124.     Exit:    function result = true if continuation header line found, else false.
  125.             *start = offset in text of start of continuation header line.
  126.             *len = length of continuation header line.
  127. ----------------------------------------------------------------------------*/
  128.  
  129. static Boolean LocateContinuationHeaderLine (Handle text, long *start, long *len)
  130. {
  131.     char *p, *pEnd, *q;
  132.     
  133.     p = *text + *start;
  134.     pEnd = *text + MyGetHandleSize(text);
  135.     while (p < pEnd && isLWSP(*p)) p++;
  136.     if (p >= pEnd || *p != CR) return false;
  137.     p++;
  138.     if (p >= pEnd || !isLWSP(*p)) return false;
  139.     while (p < pEnd && isLWSP(*p)) p++;
  140.     q = p;
  141.     while (q < pEnd && *q != CR) q++;
  142.     q--;
  143.     while (q >= p && isLWSP(*q)) q--;
  144.     q++;
  145.     *start = p - *text;
  146.     *len = q - p;
  147.     return true;
  148. }
  149.  
  150.  
  151.  
  152.  
  153. /*----------------------------------------------------------------------------
  154.     MakePathHeader 
  155.     
  156.     Make a "Path" header line.
  157.             
  158.     Exit:    function result = error code.
  159.             path = C-format "Path" header line.
  160. ----------------------------------------------------------------------------*/
  161.  
  162. static OSErr MakePathHeader (char path[261])
  163. {
  164.     OSErr err = noErr;
  165.  
  166.     err = NetGetMyName(path);
  167.     if (err == noErr) {
  168.         strcat(path, "!user");
  169.     } else if (err == userCanceledErr) {
  170.         return err;
  171.     } else {
  172.         strcpy(path, "NewsWatcher!user");
  173.     }
  174.     return noErr;
  175. }
  176.  
  177.  
  178.  
  179. /*----------------------------------------------------------------------------
  180.     MakeDateHeader 
  181.     
  182.     Make a "Date" header line.
  183.             
  184.     Exit:    date = C-format "Date" header line. Empty string if can't get 
  185.                 machine location.
  186. ----------------------------------------------------------------------------*/
  187.  
  188. static void MakeDateHeader (CStr255 date)
  189. {
  190.     static char *days[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
  191.     static char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", 
  192.                              "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
  193.  
  194.     DateTimeRec dtRec;
  195.     MachineLocation loc;
  196.     long gmtDelta;
  197.     char gmtSign;
  198.     short gmtHours;
  199.     short gmtMinutes;
  200.     
  201.     ReadLocation(&loc);
  202.     *date = 0;
  203.     if (loc.latitude == 0 && loc.longitude == 0 && loc.u.gmtDelta == 0) return;
  204.     gmtDelta = loc.u.gmtDelta & 0x00ffffff;
  205.     if ((gmtDelta >> 23) & 1) gmtDelta |= 0xff000000;
  206.     if (gmtDelta < 0) {
  207.         gmtSign = '-';
  208.         gmtDelta = -gmtDelta;
  209.     } else {
  210.         gmtSign = '+';
  211.     }
  212.     gmtHours = gmtDelta / 3600;
  213.     gmtMinutes = (gmtDelta % 3600) / 60;
  214.     GetTime(&dtRec);
  215.     sprintf(date, "%s, %.2d %s %.4d %.2d:%.2d:%.2d %c%.2d%.2d",
  216.         days[dtRec.dayOfWeek - 1],
  217.         dtRec.day,
  218.         months[dtRec.month - 1],
  219.         dtRec.year,
  220.         dtRec.hour,
  221.         dtRec.minute,
  222.         dtRec.second,
  223.         gmtSign,
  224.         gmtHours,
  225.         gmtMinutes
  226.     );
  227. }
  228.  
  229.  
  230.  
  231. /*----------------------------------------------------------------------------
  232.     MakeFromHeader 
  233.     
  234.     Make a "From" header line.
  235.             
  236.     Exit:    from = C-format "From" header line.
  237. ----------------------------------------------------------------------------*/
  238.  
  239. void MakeFromHeader (char from[514])
  240. {
  241.     MyICReadSharedPrefs(kICRealName);
  242.     MyICReadSharedPrefs(kICEmail);
  243.     
  244.     if (*gPrefs.fullName == 0) {
  245.         strcpy(from, gPrefs.emailAddress);
  246.     } else {
  247.         sprintf(from, "%s (%s)", gPrefs.emailAddress, gPrefs.fullName);
  248.     }
  249. }
  250.  
  251.  
  252.  
  253. /*----------------------------------------------------------------------------
  254.     MakeMsgIdHeader 
  255.     
  256.     Make a "Message-ID" header line.
  257.             
  258.     Exit:    function result = error code.
  259.             id = message id header line, or empty string if error.
  260. ----------------------------------------------------------------------------*/
  261.  
  262. static OSErr MakeMsgIdHeader (char id[512])
  263. {
  264.     static DateTimeRec prevDtRec = {0, 0, 0, 0, 0, 0, 0};
  265.     static short uniqueWithinSecond = 0;
  266.     DateTimeRec    dtRec;
  267.     CStr255    hostName;
  268.     OSErr err = noErr;
  269.     char *p;
  270.     short len;
  271.     
  272.     MyICReadSharedPrefs(kICEmail);
  273.  
  274.     *id = 0;
  275.     if (*gPrefs.emailAddress == 0) return noErr;
  276.     err = NetGetMyName(hostName);
  277.     if (err != noErr) {
  278.         if (err == userCanceledErr) return err;
  279.         err = NetGetMyAddrStr(hostName);
  280.         if (err != noErr) return err;
  281.     }
  282.     if (*hostName == 0)    return noErr;
  283.     for (p = gPrefs.emailAddress; *p != 0 && *p != '@' && *p != '%' &&
  284.         *p != ' ' && *p != '<' && *p != '>' && *p != ','; p++) /* do nothing */;
  285.     len = p - gPrefs.emailAddress;
  286.     GetTime(&dtRec);
  287.     if (prevDtRec.day == dtRec.day && prevDtRec.month == dtRec.month &&
  288.         prevDtRec.year == dtRec.year && prevDtRec.hour == dtRec.hour &&
  289.         prevDtRec.minute == dtRec.minute && prevDtRec.second == dtRec.second)
  290.     {
  291.         uniqueWithinSecond++;
  292.     } else {
  293.         prevDtRec = dtRec;
  294.         uniqueWithinSecond = 1;
  295.     }
  296.     sprintf(id, "<%.*s-%.2d%.2d%.2d%.2d%.2d%.2d%.4d@%s>",
  297.         len, gPrefs.emailAddress, dtRec.day, dtRec.month,
  298.         dtRec.year%100, dtRec.hour, dtRec.minute, dtRec.second, 
  299.         uniqueWithinSecond, hostName);
  300.     return noErr;
  301. }
  302.  
  303.  
  304.  
  305. /*----------------------------------------------------------------------------
  306.     AddHeader 
  307.     
  308.     Add a single header line to a header under construction.
  309.     
  310.     Entry:    key = C-format header name string, not including the 
  311.                 terminating colon.
  312.             hdrContents = pointer to header contents. Nil or empty if none.
  313.             hdrContentsLen = length of header contents.
  314.             hdr = handle to header under construction.
  315.             *hdrNext = index in header to store next line.
  316.             stripWhiteSpace = true to strip white space from header contents.
  317.             lineBreakMapping = line break mapping in header contents.
  318.             
  319.     Exit:    function result = error code.
  320.             *hdrNext updated.
  321. ----------------------------------------------------------------------------*/
  322.  
  323. static OSErr AddHeader (char *key, char *hdrContents, long hdrContentsLen, 
  324.     Handle hdr, long *hdrNext, Boolean stripWhiteSpace, 
  325.     ELineBreakMapping lineBreakMapping)
  326. {
  327.     long next, size, len, keyLen;
  328.     OSErr err = noErr;
  329.     char *start, *end, *p, *q;
  330.     
  331.     if (hdrContents == nil) return noErr;
  332.     start = hdrContents;
  333.     end = start + hdrContentsLen - 1;
  334.     while (start <= end && isLWSPorCR(*start)) start++;
  335.     while (start <= end && isLWSPorCR(*end)) end--;
  336.     hdrContentsLen = end - start + 1;
  337.     if (hdrContentsLen <= 0) return noErr;
  338.     
  339.     keyLen = strlen(key);
  340.     len = keyLen + hdrContentsLen + 3;
  341.     
  342.     next = *hdrNext;
  343.     size = MyGetHandleSize(hdr);
  344.     if (next + len > size) {
  345.         err = MySetHandleSize(hdr, next + len + 1000);
  346.         if (err != noErr) return err;
  347.     }
  348.     
  349.     q = *hdr + next;
  350.     BlockMoveData(key, q, keyLen);
  351.     q += keyLen;
  352.     BlockMoveData(": ", q, 2);
  353.     q += 2;
  354.     
  355.     p = start;
  356.     while (p <= end) {
  357.         if (stripWhiteSpace && isLWSP(*p)) {
  358.             p++;
  359.         } else if (*p == CR && lineBreakMapping != kNoMapCR) {
  360.             q--;
  361.             while (q >= *hdr && isLWSPorCR(*q)) q--;
  362.             q++;
  363.             p++;
  364.             while (p <= end && isLWSPorCR(*p)) p++;
  365.             switch (lineBreakMapping) {
  366.                 case kMapToSpace:
  367.                     *q++ = ' ';
  368.                     break;
  369.                 case kMapToComma:
  370.                     *q++ = ',';
  371.                     break;
  372.                 case kMapToCR:
  373.                     *q++ = CR;
  374.                     break;
  375.             }
  376.         } else {
  377.             *q++ = *p++;
  378.         }
  379.     }
  380.     *q++ = CR;
  381.     
  382.     *hdrNext = q - *hdr;
  383.     return noErr;
  384. }
  385.  
  386.  
  387.  
  388. /*----------------------------------------------------------------------------
  389.     AddHeaderCString 
  390.     
  391.     Add a single C-format header line to a header under construction.
  392.     
  393.     Entry:    key = C-format header name string, not including the 
  394.                 terminating colon.
  395.             hdrContents = C-format header contents. Nil or empty if none.
  396.             hdr = handle to header under construction.
  397.             *hdrNext = index in header to store next line.
  398.             stripWhiteSpace = true to strip white space from header contents.
  399.             lineBreakMapping = line break mapping in header contents.
  400.             
  401.     Exit:    function result = error code.
  402.             *hdrNext updated.
  403. ----------------------------------------------------------------------------*/
  404.  
  405. static OSErr AddHeaderCString (char *key, char *hdrContents, Handle hdr, long *hdrNext, 
  406.     Boolean stripWhiteSpace, ELineBreakMapping lineBreakMapping)
  407. {
  408.     return AddHeader(key, hdrContents, strlen(hdrContents), hdr, hdrNext,
  409.         stripWhiteSpace, lineBreakMapping);
  410. }
  411.  
  412.  
  413.  
  414. /*----------------------------------------------------------------------------
  415.     AddHeaderHandle 
  416.     
  417.     Add a single header line contained in a relocatable block to a header 
  418.     under construction.
  419.     
  420.     Entry:    key = C-format header name string, not including the 
  421.                 terminating colon.
  422.             hdrContents = handle to header contents. Nil or empty if none.
  423.             hdr = handle to header under construction.
  424.             *hdrNext = index in header to store next line.
  425.             stripWhiteSpace = true to strip white space from header contents.
  426.             lineBreakMapping = line break mapping in header contents.
  427.             
  428.     Exit:    function result = error code.
  429.             *hdrNext updated.
  430. ----------------------------------------------------------------------------*/
  431.  
  432. static OSErr AddHeaderHandle (char *key, Handle hdrContents, Handle hdr, long *hdrNext,
  433.     Boolean stripWhiteSpace, ELineBreakMapping lineBreakMapping)
  434. {
  435.     OSErr err = noErr;
  436.     char state;
  437.  
  438.     if (hdrContents == nil) return noErr;
  439.     state = MyHGetState(hdrContents);
  440.     MyHLock(hdrContents);
  441.     err = AddHeader(key, *hdrContents, MyGetHandleSize(hdrContents), hdr, hdrNext,
  442.         stripWhiteSpace, lineBreakMapping);
  443.     MyHSetState(hdrContents, state);
  444.     return err;
  445. }
  446.  
  447.  
  448.  
  449. /*----------------------------------------------------------------------------
  450.     AddExtraHeaderLines
  451.     
  452.     Add extra header lines, overriding any earlier ones.
  453.     
  454.     Entry:    extras = handle to extra header lines. Nil or empty if none.
  455.             hdr = handle to header under construction.
  456.             *hdrNext = index in header to store next line.
  457.             
  458.     Exit:    function result = error code.
  459.             *hdrNext updated.
  460. ----------------------------------------------------------------------------*/
  461.  
  462. static OSErr AddExtraHeaderLines (Handle extras, Handle hdr, long *hdrNext)
  463. {
  464.     long keyLen, contentLen;
  465.     OSErr err = noErr;
  466.     char *p, *pEnd, *q, *r, *s;
  467.     CStr255 key;
  468.     long start, len;
  469.     char state;
  470.     
  471.     state = MyHGetState(extras);
  472.  
  473.     if (extras == nil) return noErr;
  474.     MyHLock(extras);
  475.     p = *extras;
  476.     pEnd = p + MyGetHandleSize(extras);
  477.     while (p < pEnd) {
  478.         q = p;
  479.         while (q < pEnd && *q != CR && *q != ':') q++;
  480.         if (q >= pEnd || *q == CR) {
  481.             p = q+1;
  482.             continue;
  483.         }
  484.         s = r = q+1;
  485.         while (true) {
  486.             while (r < pEnd && *r != CR) r++;
  487.             if (r >= pEnd) break;
  488.             if (!isLWSP(*(r+1))) break;
  489.             r++;
  490.         }
  491.         q--;
  492.         while (q >= p && isLWSP(*q)) q--;
  493.         q++;
  494.         if (p < q) {
  495.             keyLen = q - p;
  496.             if (keyLen < 256) {
  497.                 BlockMoveData(p, key, keyLen);
  498.                 key[keyLen] = 0;
  499.                 while (s < pEnd && isLWSP(*s)) s++;
  500.                 contentLen = r - s;
  501.                 if (contentLen > 0) {
  502.                     if (LocateHeaderLine(hdr, *hdrNext, key, &start, &len)) {
  503.                         Munger(hdr, start, nil, len, s, contentLen);
  504.                         err = MemError();
  505.                         if (err != noErr) goto exit;
  506.                         *hdrNext += contentLen - len;
  507.                     } else {
  508.                         err = AddHeader(key, s, contentLen, hdr, hdrNext, false, kNoMapCR);
  509.                         if (err != noErr) goto exit;
  510.                     }
  511.                 }
  512.             }
  513.         }
  514.         p = r+1;
  515.     }
  516.  
  517. exit:
  518.  
  519.     MyHSetState(extras, state);
  520.     return err;
  521. }
  522.  
  523.  
  524.  
  525. /*----------------------------------------------------------------------------
  526.     HeaderLineIsEmpty 
  527.     
  528.     Check for an empty header line.
  529.     
  530.     Entry:    hdr = handle to header line.
  531.             
  532.     Exit:    function result = true if header line is empty (nil, or consists 
  533.                 of only space, CR, and tab characters).
  534. ----------------------------------------------------------------------------*/
  535.  
  536. Boolean HeaderLineIsEmpty (Handle hdr)
  537. {
  538.     char *p, *pEnd;
  539.  
  540.     if (hdr == nil) return true;
  541.     p = *hdr;
  542.     pEnd = p + MyGetHandleSize(hdr);
  543.     while (p < pEnd && isLWSPorCR(*p)) p++;
  544.     return p >= pEnd;
  545. }
  546.  
  547.  
  548.  
  549. /*----------------------------------------------------------------------------
  550.     MakeNewsHeader 
  551.     
  552.     Make a news article header.
  553.     
  554.     Entry:    newsgroups = Handle to "Newsgroups" header contents.
  555.             subject = Handle to "Subject" header contents.
  556.             replyto = Handle to "Reply-To" header contents. Nil or empty if none.
  557.             followupto = Handle to "Followup-To" header contents. Nil or empty if none.
  558.             keywords = Handle to "Keywords" header contents. Nil or empty if none.
  559.             distribution = Handle to "Distribution" header contents. Nil or empty if none.
  560.             extras = Handle to extra header lines. Nil or empty if none.
  561.             control = Handle to "Control" header contents. Nil or empty if none.
  562.             references = Handle to "References" header contents. Nil or empty if none.
  563.             
  564.     Exit:    function result = error code.
  565.             header = handle to header.
  566.                 
  567.     The constructed header includes a blank line at the end (CRCR).
  568. ----------------------------------------------------------------------------*/
  569.  
  570. OSErr MakeNewsHeader (Handle newsgroups, Handle subject, Handle replyto, 
  571.     Handle followupto, Handle keywords, Handle distribution, Handle extras, 
  572.     Handle control, Handle references, Handle *header)
  573. {
  574.     Handle hdr;
  575.     OSErr err = noErr;
  576.     long hdrNext;
  577.     CStr255 date;
  578.     char path[261];
  579.     char from[514];
  580.     char messageid[512];
  581.     
  582.     MyICReadSharedPrefs(kICOrganization);
  583.     
  584.     err = MyNewHandle(1000, &hdr);
  585.     if (err != noErr) return err;
  586.     hdrNext = 0;
  587.     
  588.     err = MakePathHeader(path);
  589.     if (err != noErr) goto exit;
  590.     MakeDateHeader(date);
  591.     MakeFromHeader(from);
  592.     err = MakeMsgIdHeader(messageid);
  593.     if (err != noErr) goto exit;
  594.     
  595.     err = AddHeaderCString("Path", path, hdr, &hdrNext, false, kMapToSpace);
  596.     if (err != noErr) goto exit;
  597.     err = AddHeaderCString("Date", date, hdr, &hdrNext, false, kMapToSpace);
  598.     if (err != noErr) goto exit;
  599.     err = AddHeaderCString("From", from, hdr, &hdrNext, false, kMapToSpace);
  600.     if (err != noErr) goto exit;
  601.     err = AddHeaderHandle("Newsgroups", newsgroups, hdr, &hdrNext, true, kMapToComma);
  602.     if (err != noErr) goto exit;
  603.     err = AddHeaderHandle("Followup-To", followupto, hdr, &hdrNext, true, kMapToComma);
  604.     if (err != noErr) goto exit;
  605.     err = AddHeaderHandle("Reply-To", replyto, hdr, &hdrNext, false, kMapToComma);
  606.     if (err != noErr) goto exit;
  607.     err = AddHeaderHandle("Distribution", distribution, hdr, &hdrNext, false, kMapToSpace);
  608.     if (err != noErr) goto exit;
  609.     err = AddHeaderHandle("Keywords", keywords, hdr, &hdrNext, false, kMapToSpace);
  610.     if (err != noErr) goto exit;
  611.     err = AddHeaderHandle("Subject", subject, hdr, &hdrNext, false, kMapToSpace);
  612.     if (err != noErr) goto exit;
  613.     err = AddHeaderCString("Message-ID", messageid, hdr, &hdrNext, false, kMapToSpace);
  614.     if (err != noErr) goto exit;
  615.     err = AddHeaderHandle("References", references, hdr, &hdrNext, false, kNoMapCR);
  616.     if (err != noErr) goto exit;
  617.     err = AddHeaderCString("Organization", gPrefs.organization, hdr, &hdrNext, false, kMapToSpace);
  618.     if (err != noErr) goto exit;
  619.     err = AddHeaderHandle("Control", control, hdr, &hdrNext, false, kMapToSpace);
  620.     if (err != noErr) goto exit;
  621.     err = AddExtraHeaderLines(extras, hdr, &hdrNext);
  622.     if (err != noErr) goto exit;
  623.     
  624.     err = MySetHandleSize(hdr, hdrNext+1);
  625.     if (err != noErr) goto exit;
  626.     *(*hdr + hdrNext) = CR;
  627.     
  628.     *header = hdr;
  629.     return noErr;
  630.     
  631. exit:
  632.  
  633.     MyDisposeHandle(hdr);
  634.     return err;
  635. }
  636.  
  637.  
  638.  
  639. /*----------------------------------------------------------------------------
  640.     MakeMailHeader 
  641.     
  642.     Make a mail message header.
  643.     
  644.     Entry:    subject = Handle to "Subject" header contents.
  645.             to = Handle to "To" header contents. Nil or empty if none.
  646.             cc = Handle to "Cc" header contents. Nil or empty if none.
  647.             bcc = Handle to "Bcc" header contents. Nil or empty if none.
  648.             from = Handle to "From" header contents. Nil or empty to use
  649.                 gPrefs.emailAddress (gPrefs.fullName).
  650.             copySelf = true to include self in "bcc" header line, or in "to"
  651.                 header line if to, cc, and bcc are all empty.
  652.             replyto = Handle to "Reply-To" header contents. Nil or empty if none.
  653.             keywords = Handle to "Keywords" header contents. Nil or empty if none.
  654.             newsgroups = Handle to "Newsgroups" header contents. Nil or empty if none.
  655.             followupto = Handle to "Followup-To" header contents. Nil or empty if none.
  656.             distribution = Handle to "Distribution" header contents. Nil or empty if none.
  657.             extras = Handle to extra header lines. Nil or empty if none.
  658.             references = Handle to "References" header contents. Nil or empty if none.
  659.             
  660.     Exit:    function result = error code.
  661.             header = handle to header.
  662.                 
  663.     The constructed header includes a blank line at the end (CRCR).
  664. ----------------------------------------------------------------------------*/
  665.  
  666. OSErr MakeMailHeader (Handle subject, Handle to, Handle cc, Handle bcc, Handle from,
  667.     Boolean copySelf, Handle replyto, Handle keywords, Handle extras,
  668.     Handle newsgroups, Handle followupto, Handle distribution, 
  669.     Handle references, Handle *header)
  670. {
  671.     Handle hdr = nil;
  672.     Handle xbcc = nil;
  673.     Handle xto = nil;
  674.     Boolean disposeXbcc = false;
  675.     Boolean disposeXto = false;
  676.     OSErr err = noErr;
  677.     long hdrNext;
  678.     CStr255 date;
  679.     char fromHdr[514];
  680.     long bccLen, selfLen, xbccLen;
  681.     char *q;
  682.     
  683.     MyICReadSharedPrefs(kICOrganization);
  684.     MyICReadSharedPrefs(kICEmail);
  685.     
  686.     err = MyNewHandle(1000, &hdr);
  687.     if (err != noErr) return err;
  688.     hdrNext = 0;
  689.     
  690.     MakeDateHeader(date);
  691.     
  692.     xto = to;
  693.     xbcc = bcc;
  694.     if (copySelf) {
  695.         selfLen = strlen(gPrefs.emailAddress);
  696.         if (HeaderLineIsEmpty(to) && HeaderLineIsEmpty(cc)) {
  697.             err = MyNewHandle(selfLen, &xto);
  698.             if (err != noErr) goto exit;
  699.             disposeXto = true;
  700.             BlockMoveData(gPrefs.emailAddress, *xto, selfLen);
  701.         } else {
  702.             bccLen = bcc == nil ? 0 : MyGetHandleSize(bcc);
  703.             xbccLen = bccLen + selfLen;
  704.             if (bcc != nil) xbccLen++;
  705.             err = MyNewHandle(xbccLen, &xbcc);
  706.             if (err != noErr) goto exit;
  707.             disposeXbcc = true;
  708.             q = *xbcc;
  709.             if (bcc != nil) {
  710.                 BlockMoveData(*bcc, *xbcc, bccLen);
  711.                 q += bccLen;
  712.                 *q++ = ',';
  713.             }
  714.             BlockMoveData(gPrefs.emailAddress, q, selfLen);
  715.         }
  716.     }
  717.     
  718.     err = AddHeaderCString("Date", date, hdr, &hdrNext, false, kMapToSpace);
  719.     if (err != noErr) goto exit;
  720.     if (HeaderLineIsEmpty(from)) {
  721.         MakeFromHeader(fromHdr);
  722.         err = AddHeaderCString("From", fromHdr, hdr, &hdrNext, false, kMapToSpace);
  723.     } else {
  724.         err = AddHeaderHandle("From", from, hdr, &hdrNext, false, kMapToSpace);
  725.     }
  726.     if (err != noErr) goto exit;
  727.     err = AddHeaderHandle("To", xto, hdr, &hdrNext, false, kMapToComma);
  728.     if (err != noErr) goto exit;
  729.     err = AddHeaderHandle("Cc", cc, hdr, &hdrNext, false, kMapToComma);
  730.     if (err != noErr) goto exit;
  731.     err = AddHeaderHandle("Bcc", xbcc, hdr, &hdrNext, false, kMapToComma);
  732.     if (err != noErr) goto exit;
  733.     err = AddHeaderHandle("Reply-To", replyto, hdr, &hdrNext, false, kMapToComma);
  734.     if (err != noErr) goto exit;
  735.     err = AddHeaderHandle("Subject", subject, hdr, &hdrNext, false, kMapToSpace);
  736.     if (err != noErr) goto exit;
  737.     err = AddHeaderHandle("Keywords", keywords, hdr, &hdrNext, false, kMapToSpace);
  738.     if (err != noErr) goto exit;
  739.     err = AddHeaderHandle("Newsgroups", newsgroups, hdr, &hdrNext, true, kMapToComma);
  740.     if (err != noErr) goto exit;
  741.     err = AddHeaderHandle("Followup-To", followupto, hdr, &hdrNext, false, kMapToComma);
  742.     if (err != noErr) goto exit;
  743.     err = AddHeaderHandle("Distribution", distribution, hdr, &hdrNext, false, kMapToSpace);
  744.     if (err != noErr) goto exit;
  745.     err = AddHeaderHandle("References", references, hdr, &hdrNext, false, kNoMapCR);
  746.     if (err != noErr) goto exit;
  747.     err = AddHeaderCString("Organization", gPrefs.organization, hdr, &hdrNext, false, kMapToSpace);
  748.     if (err != noErr) goto exit;
  749.     err = AddExtraHeaderLines(extras, hdr, &hdrNext);
  750.     if (err != noErr) goto exit;
  751.     
  752.     err = MySetHandleSize(hdr, hdrNext+1);
  753.     if (err != noErr) goto exit;
  754.     *(*hdr + hdrNext) = CR;
  755.     
  756.     if (disposeXto) MyDisposeHandle(xto);
  757.     if (disposeXbcc) MyDisposeHandle(xbcc);
  758.  
  759.     *header = hdr;
  760.     return noErr;
  761.     
  762. exit:
  763.  
  764.     MyDisposeHandle(hdr);
  765.     if (disposeXto) MyDisposeHandle(xto);
  766.     if (disposeXbcc) MyDisposeHandle(xbcc);
  767.     return err;
  768. }
  769.  
  770.  
  771.  
  772. /*----------------------------------------------------------------------------
  773.     FindHeaderCString
  774.     
  775.     Find and extract a message header from an article and return it as a 
  776.     C-format string.
  777.     
  778.     Entry:    text = handle to article text.
  779.             key = C-format header to locate, not including 
  780.                 the terminating ":".
  781.             maxLen = maximum length of returned header contents, including
  782.                 C-format terminating 0 byte.
  783.             
  784.     Exit:    function result = true if header found, else false.
  785.             contents = extracted C-format header contents, with leading and
  786.                 trailing white space deleted.
  787.                 
  788.     If the header content string is longer than the maximum length, it is
  789.     truncated to the maximum length.
  790. ----------------------------------------------------------------------------*/
  791.  
  792. Boolean FindHeaderCString (Handle text, char *key, char *contents, 
  793.     long maxLength)
  794. {
  795.     long start, len;
  796.     char *q;
  797.  
  798.     *contents = 0;
  799.     q = contents;
  800.     if (!LocateArticleHeaderLine(text, key, &start, &len)) return false;
  801.     while (true) {
  802.         if (len >= maxLength) len = maxLength - 1;
  803.         BlockMoveData(*text + start, q, len);
  804.         start += len;
  805.         q += len;
  806.         maxLength -= len;
  807.         if (maxLength <= 1) break;
  808.         if (!LocateContinuationHeaderLine(text, &start, &len)) break;
  809.         *q++ = ' ';
  810.         maxLength--;
  811.     }
  812.     *q = 0;
  813.     return true;
  814. }
  815.  
  816.  
  817.  
  818. /*----------------------------------------------------------------------------
  819.     FindHeaderHandle
  820.     
  821.     Find and extract a message header from an article and return it in a 
  822.     relocatable block.
  823.     
  824.     Entry:    text = handle to article text.
  825.             key = C-format header to locate, not including 
  826.                 the terminating ":".
  827.             
  828.     Exit:    function result = error code.
  829.             contents = handle to extracted header contents, with leading and
  830.                 trailing white space deleted, or nil if header not found.
  831. ----------------------------------------------------------------------------*/
  832.  
  833. OSErr FindHeaderHandle (Handle text, char *key, Handle *contents)
  834. {
  835.     long start, firstLineStart, firstLineLen, len, totalLen;
  836.     Handle h;
  837.     char *q;
  838.     OSErr err = noErr;
  839.  
  840.     *contents = nil;
  841.     if (!LocateArticleHeaderLine(text, key, &start, &len)) return noErr;
  842.     totalLen = 0;
  843.     firstLineStart = start;
  844.     firstLineLen = len;
  845.     while (true) {
  846.         totalLen += len;
  847.         start += len;
  848.         if (!LocateContinuationHeaderLine(text, &start, &len)) break;
  849.         totalLen += 1;
  850.     }
  851.     err = MyNewHandle(totalLen, &h);
  852.     if (err != noErr) return err;
  853.     MyHLock(h);
  854.     q = *h;
  855.     start = firstLineStart;
  856.     len = firstLineLen;
  857.     while (true) {
  858.         BlockMoveData(*text + start, q, len);
  859.         start += len;
  860.         q += len;
  861.         if (!LocateContinuationHeaderLine(text, &start, &len)) break;
  862.         *q++ = ' ';
  863.     }
  864.     MyHUnlock(h);
  865.     *contents = h;
  866.     return noErr;
  867. }
  868.  
  869.  
  870.  
  871. /*----------------------------------------------------------------------------
  872.     FormatAuthorName 
  873.     
  874.     Format an author name.
  875.     
  876.     Entry:    name = C-format "From" header contents.
  877.             
  878.     Exit:    name = formatted name.
  879.     
  880.     If the "From" line is in the form "address (name)", "name" is returned.
  881.     If the "From" line is in the form "name <address>", "name" is returned.
  882.     Otherwise the "From" line is unchanged.
  883.     
  884.     Note that the name is formatted in place, destroying the original 
  885.     string, and that the formatted name is always shorter or the same length
  886.     as the orginal string.
  887. ----------------------------------------------------------------------------*/
  888.  
  889. void FormatAuthorName (char *name)
  890. {
  891.     char *p, *start, *end;
  892.     short len, parenLevel;
  893.  
  894.     start = name;
  895.     end = name + strlen(name) - 1;
  896.     
  897.     p = name;
  898.     while (*p != 0 && *p != '(' && *p != '<') p++;
  899.     if (*p == '(') {
  900.         /* format = "address (name)" */
  901.         p++;
  902.         start = p;
  903.         parenLevel = 1;
  904.         while (*p != 0) {
  905.             if (*p == '(') {
  906.                 parenLevel++;
  907.             } else if (*p == ')') {
  908.                 parenLevel--;
  909.                 if (parenLevel == 0) break;
  910.             }
  911.             p++;
  912.         }
  913.         end = p-1;
  914.     } else if (*p == '<') {
  915.         /* format = "name <address>" */
  916.         end = p-1;
  917.     } else {
  918.         return;
  919.     }
  920.     if (start <= end) {
  921.         while (start < end && (isLWSP(*start) || *start == '"')) start++;
  922.         while (start < end && (isLWSP(*end) || *end == '"')) end--;
  923.         len = end - start + 1;
  924.         if (len > 0) {
  925.             BlockMoveData(start, name, len);
  926.             name[len] = 0;
  927.         }
  928.     }
  929. }
  930.  
  931.  
  932.  
  933. /*----------------------------------------------------------------------------
  934.     FindBody 
  935.     
  936.     Find the beginning of the body of an article.
  937.     
  938.     Entry:    text = handle to article text.
  939.             
  940.     Exit:    function result = offset in article of first character of
  941.                 article body, or length of article if article has no body.
  942. ----------------------------------------------------------------------------*/
  943.  
  944. long FindBody (Handle text)
  945. {
  946.     char *p, *pEnd;
  947.     long length;
  948.     
  949.     length = MyGetHandleSize(text);
  950.     p = *text;
  951.     pEnd = p + length;
  952.     while (p < pEnd) {
  953.         if (*p == CR && *(p+1) == CR) break;
  954.         p++;
  955.     }
  956.     p += 2;
  957.     while (p < pEnd && *p == CR) p++;
  958.     return p < pEnd ? p-*text : length;
  959. }
  960.  
  961.  
  962.  
  963. /*----------------------------------------------------------------------------
  964.     DeleteHeaderLine 
  965.     
  966.     Delete a header line.
  967.     
  968.     Entry:    text = handle to text.
  969.             key = keyword of header line to delete.
  970. ----------------------------------------------------------------------------*/
  971.  
  972. void DeleteHeaderLine (Handle text, char *key)
  973. {
  974.     long textLen, firstStart, len, start;
  975.  
  976.     textLen = MyGetHandleSize(text);
  977.     if (!LocateHeaderLine(text, textLen, key, &firstStart, &len)) return;
  978.     start = firstStart + len;
  979.     while (LocateContinuationHeaderLine(text, &start, &len)) start = start + len;
  980.     firstStart--;
  981.     while (firstStart >= 0 && *(*text + firstStart) != CR) firstStart--;
  982.     firstStart++;
  983.     while (start < textLen && *(*text + start) != CR) start++;
  984.     if (start < textLen) {
  985.         start++;
  986.     } else if (firstStart > 0) {
  987.         firstStart--;
  988.     }
  989.     len = start - firstStart;
  990.     Munger(text, firstStart, nil, len, "", 0);
  991. }
  992.  
  993.  
  994.  
  995. /*----------------------------------------------------------------------------
  996.     ParseRe 
  997.     
  998.     Parse the "Re:" portion of a followup subject header line.
  999.     
  1000.     Entry:    subject = pointer to subject header line.
  1001.             len = length of subject header line.
  1002.             
  1003.     Exit:    function result = length of initial "Re:" of header line.
  1004.     
  1005.     The "Re:" portion of a subject line is the inital segment of the line
  1006.     which consists of any sequence of the following: "Re:", "Re(n):",
  1007.     "Re[n]:", and "Re^n:".
  1008. ----------------------------------------------------------------------------*/
  1009.  
  1010. long ParseRe (char *subject, long len)
  1011. {
  1012.     char *p, *pEnd, *q;
  1013.     char closeBracket;
  1014.  
  1015.     p = subject;
  1016.     pEnd = p + len;
  1017.     while (p < pEnd) {
  1018.         q = p;
  1019.         if (q + 2 > pEnd) break;
  1020.         if (!MyStrNEqual(q, "Re", 2)) break;
  1021.         q += 2;
  1022.         while (q < pEnd && isLWSP(*q)) q++;
  1023.         if (q >= pEnd) break;
  1024.         if (*q == '(' || *q == '[') {
  1025.             closeBracket = *q == '(' ? ')' : ']';
  1026.             q++;
  1027.             while (q < pEnd && isLWSP(*q)) q++;
  1028.             while (q < pEnd && isdigit(*q)) q++;
  1029.             while (q < pEnd && isLWSP(*q)) q++;
  1030.             if (q >= pEnd) break;
  1031.             if (*q != closeBracket) break;
  1032.             q++;
  1033.             if (q >= pEnd) break;
  1034.         } else if (*q == '^') {
  1035.             q++;
  1036.             while (q < pEnd && isLWSP(*q)) q++;
  1037.             while (q < pEnd && isdigit(*q)) q++;
  1038.             if (q >= pEnd) break;
  1039.         }
  1040.         while (q < pEnd && isLWSP(*q)) q++;
  1041.         if (q >= pEnd) break;
  1042.         if (*q != ':') break;
  1043.         p = q + 1;
  1044.         while (p < pEnd && isLWSP(*p)) p++;
  1045.     }
  1046.     return p - subject;
  1047. }
  1048.  
  1049.  
  1050.  
  1051. /*----------------------------------------------------------------------------
  1052.     StringIsValidEmailAddress 
  1053.     
  1054.     Check a string to see if it is a syntactically valid email address (a
  1055.     non-empty string, followed by '@', followed by a non-empty string,
  1056.     followed by '.', followed by a non-empty string).
  1057.     
  1058.     Entry:    str = C-format string.
  1059.             
  1060.     Exit:    function result = true if valid email address.
  1061. ----------------------------------------------------------------------------*/
  1062.  
  1063. Boolean StringIsValidEmailAddress (char *str)
  1064. {
  1065.     char *p, *q;
  1066.     
  1067.     p = str;
  1068.     q = p;
  1069.     while (*q != 0 && *q != '@') q++;
  1070.     if (q == p || *q == 0) return false;
  1071.     p = q = q+1;
  1072.     while (*q != 0 && *q != '.') q++;
  1073.     if (q == p || *q == 0) return false;
  1074.     p = q = q+1;
  1075.     while (*q != 0) q++;
  1076.     return q > p;
  1077. }